#ifndef WWW_H
#define WWW_H

#if ENABLE_WWW

#if (WEBPLUG || UNITY_IPHONE || UNITY_ANDROID || UNITY_FLASH || UNITY_XENON || UNITY_PS3 || UNITY_WINRT)
#define WWW_USE_CURL 0
#else
#define WWW_USE_CURL 1
#endif

#define WWW_USE_BROWSER WEBPLUG && !WWW_USE_CURL

#if WWW_USE_CURL
#include "External/Curl/include/minimalcurl.h"
#endif

#include <stdlib.h>                                                                                  
#include <string.h>
#include "Runtime/Threads/Mutex.h"
#include "Runtime/Threads/Thread.h"
#include "Runtime/Threads/AtomicRefCounter.h"
#include "Runtime/GameCode/CallDelayed.h"
#include "Runtime/Utilities/LogAssert.h"
#include "Runtime/Input/TimeManager.h"
#include "Runtime/Utilities/NonCopyable.h"

/// The time since startup
double GetTimeSinceStartup ();

class Thread;
class Download;
class UnityWebStream;
class AsyncCachedUnityWebStream;
class AssetBundle;
class AudioClip;

#include <map>

enum WWWType {
	kWWWTypeCurl,
	kWWWTypeBrowser,
	kWWWTypeCached,
	kWWWTypeCrossDomainChecked,
	kWWWFlash
};

class WWW : private NonCopyable
{
private:
	UnityWebStream* m_UnityWebStream;
	bool            m_DidParseUnityWebStream;
	int				m_StreamingPosition;
	
	#if SUPPORT_REPRODUCE_LOG
	int				m_ReproRemapCount;
	#endif
	
	AudioClip*		m_AudioClip;

protected:
	bool			m_Cached;
	int				m_CacheVersion;
	UInt32			m_RequestedCRC;
	friend class WWWCached;
	
	ThreadPriority m_ThreadPriority;
	string			m_ResponseHeaders;
	void FeedUnityWebStream (bool isCompleted);
	bool SetErrorFromResponseHeaders (); // Returns true when error is set, otherwise returns false
	UInt32 GetEstimatedDownloadSize() const;

public:
	
	class AutoLock
	{
	public:
		AutoLock( WWW& www )
		: m_WWW(&www)
		{
			www.LockPartialData();
		}
		
		~AutoLock()
		{
			m_WWW->UnlockPartialData();
		}
		
	private:
		AutoLock(const AutoLock&);
		AutoLock& operator=(const AutoLock&);
		
	private:
		WWW*	m_WWW;
	};

	enum SecurityPolicy
	{
		kSecurityPolicyDontKnowYet=0,
		kSecurityPolicyAllowAccess=1,
		kSecurityPolicyDenyAccess=2
	};

	WWW (bool cached, int cacheVersion, UInt32 crc)
		// Private members (initialized by the order of their definition)
		: m_UnityWebStream(NULL)
		, m_DidParseUnityWebStream(false)
		, m_StreamingPosition(0)
	#if SUPPORT_REPRODUCE_LOG
		, m_ReproRemapCount(0)
	#endif
		, m_AudioClip(NULL)

		// Protected members
		, m_Cached(cached)
		, m_CacheVersion(cacheVersion)
		, m_RequestedCRC(crc)
		, m_ThreadPriority(kNormalPriority)
		, m_ResponseHeaders() // Initially empty

		// Private thread-safe reference counter
		, m_RefCount()
	{}

	typedef std::map<std::string,std::string> WWWHeaders;
	
	static WWW* Create (const char* url, const char * in_postData, int in_postLength, const WWWHeaders& in_headers, bool crossDomainChecked = true, bool cached = false, int cacheVersion = 0, UInt32 crc = 0);
	
	virtual const UInt8* GetData() = 0;
	virtual const UInt8* GetPartialData() const = 0;
	virtual size_t GetSize() = 0;
	virtual size_t GetPartialSize() const = 0;
	
	virtual double GetETA() const = 0; //seconds remaining until we're done.
	
	virtual void LockPartialData() = 0;
	virtual void UnlockPartialData() = 0;
		
	// Returns true when the download is complete or failed.
	virtual void Cancel() = 0;
	bool IsDone() const;
	virtual float GetProgress() const = 0;
	virtual float GetUploadProgress() const = 0;
	virtual const char* GetError() = 0;
	virtual void SetError (const std::string& error) {}
	virtual const char* GetUrl() const = 0;
	virtual std::string GetResponseHeaders();
	virtual bool HasDownloadedOrMayBlock () = 0;
	virtual void BlockUntilDone () = 0;
	virtual SecurityPolicy GetSecurityPolicy() const;

	virtual WWWType GetType () const = 0;
	
	virtual UnityWebStream* GetUnityWebStream () const;
	
	virtual bool IsCached () const;

	void CallWhenDone(DelayedCall* func, Object* o, void* userData, CleanupUserData* cleanup);
#if SUPPORT_THREADS
	ThreadPriority GetThreadPriority() const { return m_ThreadPriority; }
	virtual void SetThreadPriority( ThreadPriority priority );
#endif
	
	#if SUPPORT_REPRODUCE_LOG
	int* GetReproRemapCount () { return &m_ReproRemapCount; }
	#endif
	
	AudioClip* GetAudioClip() const { return m_AudioClip; }
	void SetAudioClip(AudioClip* clip) { m_AudioClip = clip; }

protected:
	virtual ~WWW ();

#if ENABLE_WEBPLAYER_SECURITY
	friend class WWWCrossDomainChecked;
#endif
	virtual bool IsDownloadingDone() const = 0;

public:
	/// These functions must be thread safe as they are called from the garbage collector thread
	void Retain();
	void Release();

private:
	AtomicRefCounter m_RefCount;
};

#if WWW_USE_BROWSER
class WWWBrowser : public WWW
{
	Download*   m_Download;
	std::vector<UInt8> m_Buffer;
	double      m_Eta;
	double      m_StartDownloadTime;
	std::string m_Url;
	std::string m_Error;
	std::string m_HeadersString;
	char*	m_PostData;
	size_t  m_PostLength;
	
	int     m_LockedPartialData;
	
	int GetTotalBytesUntilLoadable() const;
	
public:

	WWWBrowser (const char* postDataPtr, int postDataLength, const WWWHeaders& headers, bool cached, int cacheVersion, UInt32 crc);
		
	virtual const UInt8* GetData();
	virtual size_t GetSize();

	virtual bool IsDownloadingDone() const;

	virtual float GetProgress() const;
	virtual float GetUploadProgress() const;

	virtual const char* GetError();
	virtual void SetError (const std::string& error);

	virtual const char* GetUrl() const;
	
	virtual const UInt8* GetPartialData() const;
	virtual size_t GetPartialSize() const;
	virtual void LockPartialData();
	virtual void UnlockPartialData();
	virtual double GetETA() const; //seconds remaining until we're done.
	
	virtual bool HasDownloadedOrMayBlock ();
	virtual void BlockUntilDone ();
	virtual void Cancel ();
	
	void ForceProgressDownload ();

	virtual WWWType GetType () const { return kWWWTypeBrowser; }
	static int ProgressDownload(Download* download);

public:
	static WWWBrowser* CreateBrowser (const char* url, const char * in_postData, int in_postLength, const WWWHeaders& in_headers, bool cached, int cacheVersion, UInt32 crc);

protected:
	virtual ~WWWBrowser ();
};
#endif // WWW_USE_BROWSER


#if WWW_USE_CURL

class WWWCurl : public WWW
{
	private:
		size_t alloc_size;
		curl_slist* curlHeaders;
		curl_slist* GetHeaderSList () ;

		Mutex mutex;
		size_t size;
		UInt8* data;
		char* errorBuffer;
		bool  abortDownload;
		float progress;
		float uploadProgress;
		unsigned totalSize;
		double eta;
		double startTime;
		
		char* postData;
		int   postLength; // -1 for GET requests
		int   postPosition; 
		
		WWWHeaders requestHeaders; 
		
		size_t AppendBytes(void * moreData, size_t bytes);
		size_t PostBytes(void * moreData, size_t bytes);
		
		CURLcode GetURL( const char* url );

		Thread thread;
		char* url;
		
		int   result;

		void DoInit( const char* in_url, const char * in_postData, int in_postLength, const WWWHeaders& in_headers);
		void Cleanup();
		
		static size_t WriteCallback(void * data, size_t size, size_t elements, WWWCurl * myData);
		static size_t ReadCallback(void * data, size_t size, size_t elements, WWWCurl * myData);
		static size_t HeaderCallback(void * data, size_t size, size_t elements, WWWCurl * myData);
		static int ProgressCallback (WWWCurl *myData, double dltotal, double dlnow, double ultotal, double ulnow);

	public:

		WWWCurl( const char* in_url, const char * in_postData, int in_postLength, const WWWHeaders& in_headers,  bool cached, int cacheVersion, UInt32 crc );

		virtual bool IsDownloadingDone() const;
		virtual const UInt8* GetData();
		virtual const char* GetError();
		virtual void SetError (const std::string& error);
		virtual const char* GetUrl() const;
		virtual size_t GetSize();
		virtual const UInt8* GetPartialData() const;
		virtual size_t GetPartialSize() const;
		virtual float GetProgress() const;
		virtual float GetUploadProgress() const;
		virtual double GetETA() const;
		
		virtual void LockPartialData();
		virtual void UnlockPartialData();
		virtual bool HasDownloadedOrMayBlock ();

		virtual void Cancel ();
		virtual void SetThreadPriority( ThreadPriority priority );
		void BlockUntilDone();

		virtual WWWType GetType () const { return kWWWTypeCurl; }
		virtual std::string GetResponseHeaders();

	protected:
		~WWWCurl();

	private:
		void StartThread();
		static void* WWW_ThreadEntryPoint(void* data);
		UInt32 GetEstimatedDownloadSize() const;
};
#endif // WWW_USE_CURL

class WWWDelayCall {
	private:
		WWW* m_wait_for;
		DelayedCall* m_func;
		Object* m_o;
		void * m_userData;
		CleanupUserData* m_cleanup;
	public:
		WWWDelayCall(WWW* www, DelayedCall* func, Object* o, void* userData, CleanupUserData* cleanup);
		~WWWDelayCall();
		static void Callback(Object* o, void* userData);
		static void Cleanup(void* userData);
		static bool MatchForCancel(void* callBackUserData, void* cancelUserData);
};

#if ENABLE_WEBPLAYER_SECURITY

void ProcessCrossDomainRequestsFromNonMainThread();

class WWWCrossDomainCheckedImpl;
class WWWCrossDomainChecked : public WWW
{
private:
	const char* m_PostData;
	int m_PostDataLength;
	std::string m_PostDataDataCopy;
	WWWHeaders m_Headers;

	WWWCrossDomainCheckedImpl* m_CrossChecker;
	WWW* m_WWW;

	bool CanCreateDownloader() const;
	bool RequestLooksSafeEnoughToMakeWithoutPolicyAccess() const;
	void StartEmbeddedDownload();
	void BlockedStartEmbeddedDownload();

public:
	WWWCrossDomainChecked (const char* url, const char* postData, int postDataLength, const WWWHeaders& headers, bool cached, int cacheVersion, UInt32 crc);

	virtual SecurityPolicy GetSecurityPolicy() const;
	virtual UnityWebStream* GetUnityWebStream() const;
	virtual const UInt8* GetData();
	virtual const UInt8* GetPartialData() const;
	virtual size_t GetSize();
	virtual size_t GetPartialSize() const;
	
	virtual double GetETA() const;
	
	virtual bool IsCached () const;

	virtual void LockPartialData();
	virtual void UnlockPartialData();
		
	// Returns true when the download is complete or failed.
	virtual void Cancel();
	virtual bool IsDownloadingDone() const;
	virtual float GetProgress() const;
	virtual float GetUploadProgress() const { return 0.f; }
	virtual const char* GetError();
	virtual const char* GetUrl() const;
	virtual std::string GetResponseHeaders();
	virtual bool HasDownloadedOrMayBlock ();
	virtual void BlockUntilDone ();

	virtual void SetThreadPriority( ThreadPriority priority );

	virtual WWWType GetType () const { return kWWWTypeCrossDomainChecked; }

protected:
	~WWWCrossDomainChecked ();
};
#endif  //ENABLE_WEBPLAYER_SECURITY

// Constant error strings
extern const char* kWWWErrCustomHeadersWithGET;
extern const char* kWWWErrZeroPostData;
extern const char* kWWWErrNULLPostDataWithPositiveLength;
extern const char* kWWWErrCancelled;
extern const char* kWWWErrPostDataWithNonHTTPSchema;
extern const char* kWWWErrHeadersWithNonHTTPSchema;

double CalculateEta (int downloadedBytes, int totalBytes, double startTime);

const char* GetCachedWWWError(const WWW& www, std::string& err);

#if !UNITY_FLASH

std::string DecodeEscapedURL(const std::string& url);

#endif

#endif // ENABLE_WWW

#endif // WWW_H
